package com.security.config.security.authentication;

import com.security.config.security.authentication.common.*;
import com.security.config.security.authentication.sms.SmsAuthenticationConfigurer;
import com.security.config.security.authentication.username.UsernameAuthenticationConfigurer;
import com.security.service.impl.AccountServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;

/**
 * Spring Security 配置类
 *
 * @author DblSun
 */
@SuppressWarnings("all")
@EnableMethodSecurity
@Configuration
public class SecurityConfig {
    @Resource
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    @Resource
    private MyAccessDeniedHandler myAccessDeniedHandler;
    @Resource
    private MyLogoutSuccessHandler myLogoutSuccessHandler;
    @Resource
    private AccountServiceImpl accountService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private JwtAuthenticationFilter jwtAuthenticationFilter;
    @Resource
    private MySessionExpiredHandler mySessionExpiredHandler;

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 跨域配置
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        // 基于路径的跨域配置
        UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource =
                new UrlBasedCorsConfigurationSource();
        // 配置跨域
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedOrigins(Arrays.asList("*")); // 允许所有来源  http://localhost:8081
        corsConfiguration.setAllowedHeaders(Arrays.asList("*")); // 允许所有请求头 Content-Type Authorization
        corsConfiguration.setAllowedMethods(Arrays.asList("*")); // 允许所有请求方法 post get put delete
        // 注册跨域配置
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return urlBasedCorsConfigurationSource;
    }

    /**
     * Spring Security 监听 session 的销毁事件来触发会话信息的清理工作
     */
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }

    /**
     * 基于用户名密码的登录配置
     */
    @Bean
    public SecurityFilterChain firstFilterChain(HttpSecurity httpSecurity,
                                                CorsConfigurationSource corsConfigurationSource) throws Exception {
        return httpSecurity
                .securityMatcher("/username/**")
                .csrf(AbstractHttpConfigurer::disable)
                .formLogin(AbstractHttpConfigurer::disable)
                .anonymous(AbstractHttpConfigurer::disable)
                .httpBasic(AbstractHttpConfigurer::disable)
                // 不需要重定向到登录页面，因为我们使用了自定义的登录过滤器a
                .requestCache(AbstractHttpConfigurer::disable)
                .cors(cors -> cors.configurationSource(corsConfigurationSource))
                .authorizeHttpRequests(auth -> auth
                        .anyRequest().authenticated()
                )
                .logout(logout -> logout
                        .logoutUrl("/username/logout")
                        .logoutSuccessHandler(myLogoutSuccessHandler)
                        .permitAll()
                )
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                        .maximumSessions(1)
                        .maxSessionsPreventsLogin(true) // 阻止超过最大会话数的会话登录
                        .expiredSessionStrategy(mySessionExpiredHandler)
                )
                .exceptionHandling(exception -> exception
                        .authenticationEntryPoint(myAuthenticationFailureHandler)
                        .accessDeniedHandler(myAccessDeniedHandler)
                )
                .with(new UsernameAuthenticationConfigurer(accountService, bCryptPasswordEncoder(), stringRedisTemplate),
                        configurer -> configurer.setBuilder(httpSecurity))
                .addFilterAfter(jwtAuthenticationFilter, LogoutFilter.class)
                .build();
    }

    /**
     * 基于手机号验证码的登录配置
     */
    @Bean
    public SecurityFilterChain secondFilterChain(HttpSecurity httpSecurity,
                                                 CorsConfigurationSource corsConfigurationSource) throws Exception {
        return httpSecurity
                .securityMatcher("/phone/**")
                .csrf(AbstractHttpConfigurer::disable)
                .formLogin(AbstractHttpConfigurer::disable)
                .logout(AbstractHttpConfigurer::disable)
                .anonymous(AbstractHttpConfigurer::disable)
                .httpBasic(AbstractHttpConfigurer::disable)
                // 不需要重定向到登录页面，因为我们使用了自定义的登录过滤器a
                .requestCache(AbstractHttpConfigurer::disable)
                .cors(cors -> cors.configurationSource(corsConfigurationSource))
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/captcha").permitAll()
                        .anyRequest().authenticated()
                )
                .logout(logout -> logout
                        .logoutUrl("/phone/logout")
                        .logoutSuccessHandler(myLogoutSuccessHandler)
                        .permitAll()
                )
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                        .maximumSessions(1)
                        .maxSessionsPreventsLogin(true) // 后登录的用户会使之前的用户登录失效
                        .expiredSessionStrategy(mySessionExpiredHandler)
                )
                .exceptionHandling(exception -> exception
                        .authenticationEntryPoint(myAuthenticationFailureHandler)
                        .accessDeniedHandler(myAccessDeniedHandler)
                )
                .with(new SmsAuthenticationConfigurer(accountService, stringRedisTemplate),
                        configurer -> configurer.setBuilder(httpSecurity))
                .addFilterAfter(jwtAuthenticationFilter, LogoutFilter.class)
                .build();
    }
}
